package org.activequant.broker.ib;

import java.util.HashMap;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.account.BrokerAccount;
import org.activequant.core.domainmodel.account.Position;
import org.activequant.core.types.Triple;
import org.activequant.util.exceptions.ValueNotFoundException;
import org.activequant.util.ib.IBEventListener;
import org.activequant.util.ib.IBTwsConnection;

import com.ib.client.Contract;
import com.ib.client.Execution;
import com.ib.client.Order;
import com.ib.client.OrderState;

/**
 * 
 * Account managing broker implementation.<br>
 * <br>
 * IBAccountManagingBroker extends IBBroker. Defines the IBPosition class. Holds the following associated variables:
 * <ul>
 * <li>brokerAccount(BrokerAccount)</li>
 * <li>accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;)</li>
 * </ul>
 * Holds the following inherited variables:
 * <ul>
 * <li>connection(IBTwsConnection)</li>
 * <li>ibEventListener(IBEventListener)</li>
 * <li>orderMap(Map&lt;Integer,OrderTracker&gt;)</li>
 * <li>timeStampGenerator(UniqueDateGenerator)</li>
 * <li>logger(IAQLogger)</li>
 * <li>orderMap(Map&lt;String,OrderTrackerBase&gt;)</li>
 * </ul>
 * <b>History:</b><br>
 *  - [03.05.2008] Created (Ulrich Staudinger)<br>
 *
 *  @author Ulrich Staudinger
 */
public class IBAccountManagingBroker extends IBBroker {
	/**
	 * public static final String MARKET_PRICE = "marketPrice";
	 */
	public static final String MARKET_PRICE = "marketPrice";
	/**
	 * public static final String MARKET_VALUE = "marketValue";
	 */
	public static final String MARKET_VALUE = "marketValue";
	/**
	 * public static final String AVG_COST = "avgCost";
	 */
	public static final String AVG_COST = "avgCost";
	/**
	 * public static final String UNREALIZED_PNL = "unrealizedPNL";
	 */
	public static final String UNREALIZED_PNL = "unrealizedPNL";
	/**
	 * public static final String REALIZED_PNL = "realizedPNL";
	 */
	public static final String REALIZED_PNL = "realizedPNL";
	/**
	 * public static final String ACCOUNT_NAME = "accountName";
	 */
	public static final String ACCOUNT_NAME = "accountName";
	/**
	 * private final BrokerAccount brokerAccount;
	 */
	private final BrokerAccount brokerAccount;
	
	/**
	 * private HashMap&lt;String, Triple&lt;String, String, String&gt;&gt; accountValues = new HashMap&lt;String, Triple&lt;String, String, String&gt;&gt;();<br/>
	 * holds the value triples received from IB.
	 */
	private HashMap<String, Triple<String, String, String>> accountValues = new HashMap<String, Triple<String, String, String>>();
	
	/**
	 * IBPosition extends Position. Holds the following associated variable:
	 * <ul>
	 * <li>additionalInformation(HashMap&lt;String,String&gt;)</li>
	 * </ul>
	 * Holds the following inherited variables:
	 * <ul>
	 * <li>id(Long)</li>
	 * <li>instrumentSpecification(InstrumentSpecification)</li>
	 * <li>averagePrice(double)</li>
	 * <li>quantity(double)</li>
	 * </ul>
	 * @author Dimitar
	 *
	 */
	public class IBPosition extends Position {
		/**
		 * private HashMap&lt;String,String&gt; additionalInformation = new HashMap&lt;String,String&gt;();
		 */
		private HashMap<String, String> additionalInformation = new HashMap<String, String>();
		/**
		 * constructs a IBPosition(extends Position) using the given spec(InstrumentSpecification), a(double) and b(double)
		 * to set its inherited instrumentSpecification(InstrumentSpecification), averagePrice(double) and quantity(double)
		 * @param spec
		 * @param a
		 * @param b
		 */
		IBPosition(InstrumentSpecification spec, double a, double b){
			super(spec, a, b);
		}
		/**
		 * returns the associated additionalInformation(HashMap&lt;String,String&gt;)
		 * @return
		 */
		public HashMap<String, String> getAdditionalInformation() {
			return additionalInformation;
		}
		/**
		 * sets the associated additionalInformation(HashMap&lt;String,String&gt;) with the given additionalInformation(HashMap&lt;String,String&gt;)
		 * @param additionalInformation
		 */
		public void setAdditionalInformation(HashMap<String, String> additionalInformation) {
			this.additionalInformation = additionalInformation;
		}
		
	}
	
	/**
	 * constructs an IBAccountManagingBroker(extends IBBroker) using the given account(BrokerAccount) and connection(IBTwsConnection) to set its associated brokerAccount(BrokerAccount) and inherited connection(IBTwsConnection).<br/>
	 * Connects to the TWS by invoking <code>connection.connect()</code> on that connection(IBTwsConnection).<br/>
	 * Adds the inherited ibEventListener(IBEventListener) to the ibEventListeners(Queue&lt;IBEventListener&gt;) (if its not already there) if the associated connection(IBTwsConnection).<br/>
	 * Adds another IBEventListener to the ibEventListeners(Queue&lt;IBEventListener&gt;) whose job is to update Positions in the associated brokerAccount(BrokerAccount)
	 * and updates account information in the associated accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;)
	 * @param account
	 * @param connection
	 */
	public IBAccountManagingBroker(BrokerAccount account, IBTwsConnection connection){
		super(connection);
		this.brokerAccount = account; 

		// adding the ib event listener for managing the broker account. 
		connection.addIBEventListener(new IBEventListener(){
			/**
			 * doing nothing as this is handled in IBBroker.
			 */
			public void error(int orderId, int errorCode, String message) {
				//log.error(""+orderId+" / "+ errorCode+" / "+message);
				// doing nothing as this is handled in IBBroker. 
			}

			/**
			 * received in case of an execution. <br/>
			 * doing nothing as this is handled in IBBroker.
			 */
			public void execDetails(int reqId, Contract arg1, Execution arg2) {
				//log.info(""+reqId+" / "+ arg1+" / "+arg2);
				// doing nothing as this is handled in IBBroker. 				
			}
			
			/**
			 * this method gets called after (possibly multiple) call to execDetails
			 */
			public void execDetailsEnd(int reqId){
				
			}
			/**
			 * received when an order has been opened. <br/>
			 * doing nothing as this is handled in IBBroker.
			 */
			public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
				//log.info(orderId+ " / " + contract + " / " +order);
				// doing nothing as this is handled in IBBroker. 			
			}

			/**
			 * order status ...<br/>
			 * doing nothing as this is handled in IBBroker.
			 */
			public void orderStatus(int orderId, String arg1, int arg2, int arg3, double arg4, int arg5, int arg6, double arg7, int arg8, String whyHeld) {
				//log.info(orderId+ " / " + arg1+ " / " + arg2+ " / " + arg3+ " / " + arg4+ " / " + arg5+ " / " + arg6+ " / " + arg7+ " / " + arg8);
				// doing nothing as this is handled in IBBroker. 
			} 
			/**
			 * update the account value.<br/>
			 * <strong>1.</strong> Creates a triple(Triple&lt;String,String,String&gt;) with the given amount(String), currency(String) and account(String).<br/>
			 * <strong>2.</strong> Maps that triple(Triple&lt;String,String,String&gt;) into the accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;) using the given informationType(String) as key.
			 */
			public void updateAccountValue(String informationType, String amount, String currency, String account) {
				log.info("[updateAccountValue] "+informationType+ " / " + amount + " / " + currency + " / " + account);
				Triple<String, String, String> triple = new Triple<String, String, String>(amount, currency, account);
				accountValues.put(informationType, triple);
			}
			
			/**
			 * <strong>1.</strong> If the given positionCount(int) is 0, it removes the position(Position) for the given instrument(Contract) in the portfolio(Portfolio) of the 
			 * associated brokerAccount(BrokerAccount).<br/>
			 * <strong>2.</strong> Otherwise, it updates(or adds) the same IBPosition, and updates it with the given marketPrice(double), positionCount(int),
			 * accountName(String), realizedPNL(double), unrealizedPNL(double), avgCost(double) and marketValue(double).<br/>
			 * @param marketPrice contains the market price INCLUDING commission, IB doesn't push the paid premium without commission.  
			 * 
			 */
			public void updatePortfolio(Contract instrument, int positionCount, double marketPrice, double marketValue, double avgCost, double unrealizedPNL, double realizedPNL, String accountName) {
				log.info("[updatePortfolio] " + instrument.m_symbol + "/ pos sum : " + positionCount + " / mkt price:" + marketPrice + " / mkt value:" + marketValue + " / avg cost:" + avgCost + " / Unrlzd P&L:" + unrealizedPNL + " / P&L:" + realizedPNL + " / " + accountName);				
				try{
					InstrumentSpecification spec = IBTwsConnection.convertToInstrument(instrument);
					if(positionCount == 0){
						try{
							// position has been closed out.
							Position pos = brokerAccount.getPortfolio().getPosition(spec);
							if(pos!=null){
								brokerAccount.getPortfolio().removePosition(pos);
							}
						}
						catch(ValueNotFoundException c){
							// no position in portfolio, can be ignored. 
						}
					}
					else{
						// update of existing or new position. 
						IBPosition pos = null;
						try{
							pos = (IBPosition)brokerAccount.getPortfolio().getPosition(spec);
							pos.setAveragePrice(avgCost);//was marketPrice instead of avgCost (wrong)
							pos.setQuantity((double)positionCount);
						}
						catch(ValueNotFoundException x){
							// no position in portfolio, can be ignored.
							pos = new IBPosition(spec, avgCost, (double)positionCount);//was marketPrice instead of avgCost (wrong)
							// add the position to the broker account.
							brokerAccount.getPortfolio().addPosition(pos);
						}
						
						// set the IB specific fields. 
						((IBPosition)pos).getAdditionalInformation().put(ACCOUNT_NAME, accountName);
						((IBPosition)pos).getAdditionalInformation().put(REALIZED_PNL, ""+realizedPNL);
						((IBPosition)pos).getAdditionalInformation().put(UNREALIZED_PNL, ""+unrealizedPNL);
						((IBPosition)pos).getAdditionalInformation().put(AVG_COST, ""+avgCost);
						((IBPosition)pos).getAdditionalInformation().put(MARKET_VALUE, ""+marketValue);
						((IBPosition)pos).getAdditionalInformation().put(MARKET_PRICE, ""+marketPrice);
					}
				}
				catch(Exception x){
					log.warn("Exception while converting updating portfolio", x);
				}
			}
		});
	}
	/**
	 * returns the associated accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;)
	 * @return
	 */
	public HashMap<String, Triple<String, String, String>> getAccountValues() {
		return accountValues;
	}
	/**
	 * sets the associated accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;) with the given accountValues(HashMap&lt;String, Triple&lt;String,String,String&gt;&gt;)
	 * @param accountValues
	 */
	public void setAccountValues(HashMap<String, Triple<String, String, String>> accountValues) {
		this.accountValues = accountValues;
	}
	/**
	 * returns the associated brokerAccount(BrokerAccount)
	 * @return
	 */
	public BrokerAccount getBrokerAccount(){
		return this.brokerAccount;
	}
}

